
import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;

import org.jbox2d.collision.AABB;
import org.jbox2d.collision.Shape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.joints.DistanceJoint;
import org.jbox2d.dynamics.joints.MouseJoint;
import org.jbox2d.dynamics.joints.RevoluteJoint;
import org.jbox2d.util.nonconvex.Polygon;

import pulpcore.CoreSystem;
import pulpcore.Stage;
import pulpcore.animation.Fixed;
import pulpcore.image.BlendMode;
import pulpcore.image.CoreFont;
import pulpcore.image.CoreGraphics;
import pulpcore.Input;
import pulpcore.scene.Scene2D;
import pulpcore.sound.Sound;
import pulpcore.sprite.Button;
import pulpcore.sprite.Group;
import pulpcore.sprite.ImageSprite;
import pulpcore.sprite.Label;
import pulpcore.sprite.Sprite;
import pulpfizz.physics.Actor;
import pulpfizz.physics.BodyUtils;
import pulpfizz.physics.JointUtils;
import pulpfizz.physics.Material;
import pulpfizz.physics.NewWorld;
import pulpfizz.physics.PhysicsTileMap;
import pulpfizz.pulp.CameraControlledScene;
import pulpfizz.pulp.CameraSprite;
import pulpfizz.pulp.body.BodySprite;
import pulpfizz.pulp.body.ImageBodySprite;
import pulpfizz.pulp.body.Java2DBodySprite;
import pulpfizz.pulp.body.PhysicsLayer;
import pulpfizz.pulp.body.SimpleBodyDrawer;
import pulpfizz.pulp.ui.LabelBox;
import pulpfizz.pulp.ui.SmoothMouse;

/**
 * A simple, over-commented demonstration that shows you how to create a world.
 * When running the example, the arrow keys move the camera, the mouse wheel
 * zooms the camera, and a mouse click will set off a bomb at the mouse location.
 * 
 * @author eric
 *
 */
public class BasicDemo extends CameraControlledScene
{
	/* 
	 * CameraControlledScene lets us pan and zoom a PulpFizz scene
	 * and do things like follow a particular body.
	 * 
	 * It's usually best to base PulpFizz scenes on this class,
	 * though it's not strictly necessary; at the moment, CameraControlledScene
	 * is specific to PulpFizz and cannot be used apart from the physics
	 * handling.  This may be relaxed in a future version.
	 */
	
	/*
	 * Your PhysicsLayer does most of the heavy lifting,
	 * making sure that physical objects stay synchronized
	 * with their sprites, and actually running the physical
	 * simulation.
	 */
	private PhysicsLayer physics;
	
	/* 
	 * This stuff is not strictly necessary in PulpFizz,
	 * but is useful for this example. 
	 */
	private SmoothMouse sm;
	private LabelBox fpsSprite;
	private Button resetButton;
	
	/* Frame rate counter variables. */
	int timeElapsed = 0, numFrames=0;
    
    @Override
	public void load()
	{
        super.load();
        Stage.setFrameRate(60);

		/* 
		 * Create a Physics sprite, with objects displaying at 20x their size.
		 * This is important because JBox2d uses meters as units, and a meter
		 * should almost always never take up just one pixel on screen.
		 */
		physics = new PhysicsLayer(20.0);
		
		/* Set the anchor to the center, and add it to the stage. */
        physics.setAnchor(Sprite.CENTER);
		add(physics);
		
		/* 
		 * Size the physics world so it matches the stage.
		 * Sometimes you might want to size the world differently,
		 * we'll see an example of that in a later demo.
		 */
		physics.fitToStage();
		
		/* 
		 * Set the gravity to roughly Earth's values.
		 * Notice that negative y means "down" in the physics world.
		 */
        physics.setGravity(new Vec2(0,-10.0f));
        
        /* Add a smoothed mouse tracking sprite. */ 
        sm = new SmoothMouse();
		add(sm);
		
		/* 
		 * We can add sprites to a heads up display that stays put even
		 * if the camera moves.  This one displays the FPS count.
		 */
		fpsSprite = new LabelBox("",10,10);
		addToHUD(fpsSprite);
		
		/* ...and a reset button.  You can also reset with 'r' */
		resetButton = Button.createLabeledButton("Reset", Stage.getWidth()-10, 10);
        resetButton.setAnchor(Sprite.NORTH_EAST);
        addToHUD(resetButton);
        
		/* The actual physical world is constructed here. */
		createWorld();
	}
    
	public void createWorld()
	{
		NewWorld world = physics.getWorld();

		/* Add a physical border to the world so our objects can't escape. */
        Body border = BodyUtils.createBoundingBox(world, 0.5f);
        
        /* 
         * If you don't do anything else to a newly created body, it will
         * be fixed in space, which is what we want for the border.
         */
        
        /* 
         * The body for the border is created, now we need to attach a graphic.
         * Java2DBodySprite has a default drawing mode that just renders the
         * geometry.  In another example we'll see how to add your own art, it's
         * very easy to do.
         */
        Java2DBodySprite borderSprite = new Java2DBodySprite(border, physics);
		physics.add(borderSprite);
		
        /* Some variables to make sure we create bodies within the world bounds. */
        AABB worldAABB = world.getWorldAABB();
        float minX = worldAABB.lowerBound.x + 2.0f;
        float widthX = worldAABB.upperBound.x - minX - 4.0f;
        float minY = worldAABB.lowerBound.y + 2.0f;
        float heightY = worldAABB.upperBound.y - minY - 4.0f;
        
        /* Create a bunch of circles. */
        for (int i=0; i<30; ++i) {
        	float myX = minX + (float)(Math.random()*widthX);
        	float myY = minY + (float)(Math.random()*heightY);
        	float myRad = 0.2f + .5f*(float)Math.random();
        	
        	/* Actually create the circle. */
        	Body myCircle = BodyUtils.createCircle(world, myX, myY, myRad);
        	
        	/* 
        	 * Apply a material to the circle.  This sets properties like
        	 * mass and friction.
        	 */
        	Material m = Material.PLASTIC();
        	/*
        	 * Get in the habit of calling applyAll() when applying
        	 * material properties - this will apply the material to every
        	 * shape on the body (a body may have multiple shapes attached
        	 * to it), whereas apply() only applies Body-specific properties,
        	 * leaving out things like density, friction and restitution,
        	 * which "live" on the attached shapes.
        	 */
    		m.applyAll(myCircle);
        	
        	/* Same as what we did to add a graphic to the border... */
    		Java2DBodySprite spr = new Java2DBodySprite(myCircle, physics);
    		physics.add(spr);
        }
        
        /* Create a bunch of squares.  Mostly the same as above. */
        for (int i=0; i<30; ++i) {
        	float myX = minX + (float)(Math.random()*widthX);
        	float myY = minY + (float)(Math.random()*heightY);
        	float mySize = 0.2f + (float)Math.random();
        	float myAngle = (float)(2*Math.PI*Math.random());
        	Body myBox = BodyUtils.createBox(world, myX, myY, mySize, mySize, myAngle);
        	Material m = Material.PLASTIC();
    		m.applyAll(myBox);
    		
    		/* 
    		 * You can also set the sharpness of the rendering and the
    		 * coloring of the Java2DBodySprite.
    		 */
    		float sharpness = 3.0f; //much sharper than default 1.0, so zooming in will look clearer
    		Color myStroke = Color.RED;
    		Color myFill = Color.BLUE;
    		Java2DBodySprite spr = new Java2DBodySprite(myBox, physics, sharpness, myStroke, myFill);
    		physics.add(spr);
        }
        
        /* Create a bunch of random polygons. */
        Polygon.B2_POLYGON_REPORT_ERRORS = false; //turn off polygon error reporting, there's no need for it here
        for (int i=0; i<30; ++i) {
        	float myX = minX + (float)(Math.random()*widthX);
        	float myY = minY + (float)(Math.random()*heightY);
        	float mySize = 2.0f;
        	int nVerts = 3 + (int)(Math.random()*20);
        	Vec2[] myVerts = new Vec2[nVerts];
        	for (int j=0; j<nVerts; ++j) {
        		myVerts[j] = new Vec2(myX + mySize * (float)(Math.random()-0.5),
        							  myY + mySize * (float)(Math.random()-0.5));
        	}
        	
        	/*
        	 * createPolygon takes an arbitrary list of vertices,
        	 * and does its best to create a valid polygon out of
        	 * it.  It fixes up things like non-counterclockwise
        	 * ordering, non-convexity, and non-simplicity, all of
        	 * which you would otherwise have to fix yourself.
        	 * 
        	 * However, the flipside of this automatic fix-up is
        	 * that JBox2d's restrictions on shapes cannot always
        	 * be satisfied, so sometimes the thing just gives up
        	 * and doesn't create pieces of your polygon.  It will
        	 * still do its best to create as much valid geometry
        	 * as possible, but particularly small features are
        	 * often skipped due to engine restrictions.
        	 */
        	Body myPoly = BodyUtils.createPolygon(world, myVerts);
        	
        	/*
        	 * If you pass weird geometry to createPolygon, the result
        	 * might come back null if it can't be properly sanitized
        	 * as geometry.  Here we're making random shapes, so this
        	 * is a possibility.  So always check for null!
        	 * 
        	 * You can probably omit the null check if you are creating
        	 * fairly fat convex polygons (where "fairly fat" means
        	 * greater than ~0.1 m minimum thickness), but otherwise
        	 * you should always check, just to be safe, ESPECIALLY
        	 * if you're accepting user input.
        	 */
        	if (myPoly == null) continue;
        	Material m = Material.PLASTIC();
    		m.applyAll(myPoly);
    		
    		float sharpness = 3.0f;
    		Color myStroke = Color.GREEN;
    		Color myFill = Color.GREEN;
    		
    		Java2DBodySprite spr = new Java2DBodySprite(myPoly, physics, sharpness, myStroke, myFill);
    		physics.add(spr);
    		
    		/*
    		 * The BodyUtils class has useful methods in addition to the body
    		 * creation routines - here we'll use the cloneBodyWithOffset
    		 * method to clone the body we've just created and shift its
    		 * location by (2,0).
    		 * 
    		 * This method can be very useful for creating chains of objects,
    		 * but we'll have to wait until we encounter joints (in a later
    		 * example) to see that.
    		 */
    		Body myPolyClone = BodyUtils.cloneBodyWithOffset(2.0f, 0.0f, myPoly);
    		myStroke = Color.RED;
    		myFill = Color.RED;
    		spr = new Java2DBodySprite(myPolyClone, physics, sharpness, myStroke, myFill);
    		physics.add(spr);
        }
        
	}
	
	   
    @Override
	public void update(int elapsedTime)
	{
		super.update(elapsedTime);
		
		if (resetButton.isClicked() || Input.isPressed(Input.KEY_R)) {
			//Reset the physics
			physics.resetWorld();
			
			//Remove all the sprites
			physics.removeAll();
			
			// The PhysicsLayer is still fit to the original size
			// of the stage, so we don't need to reset it.
			// Also, the camera is not reset.
	        createWorld();
		}
		
		// Simple FPS counter handling - update value every second
		// with number of frames simulated during that second.
        timeElapsed += elapsedTime;
        numFrames++;
        if(timeElapsed >= 1000) {
            timeElapsed = 0;
            fpsSprite.setText("FPS="+ numFrames);
            numFrames=0;
        }
        
        /*
         * If the mouse is clicked, we want to set off a "bomb"
         * at the click point.  
         */
        if (Input.isMousePressed() && physics != null) {
        	/* Get the physics location of the mouse based on screen location. */
			double pX = physics.getPhysicsX(sm.x.get(), sm.y.get());
			double pY = physics.getPhysicsY(sm.x.get(), sm.y.get());
			
			/* Put it into a vector. */
			Vec2 mouseClickWorld = new Vec2( (float)pX, (float)pY );
			
			/* 
			 * Get all bodies within 2.0 meters of the mouse click.
			 * This would be a MAJOR hassle without this utility method!
			 */
			Body[] bList = BodyUtils.getAllBodiesInCircle(physics.getWorld(), mouseClickWorld, 2.0f);
			
			if (bList != null) { //the query may return null, so always check it!
				for (int i=0; i<bList.length; ++i) {
					Body b = bList[i];
					
					/* 
					 * Get direction and scale the impulse
					 * based on mass of object and a 1/r^2
					 * dependence on distance.
					 */
					Vec2 imp = mouseClickWorld.sub(b.getWorldCenter());
					/*
					 * We add .1f to the actual length to serve both as
					 * a protection against dividing by zero and a damping
					 * of extremely high impulses.
					 */
					float dist = imp.normalize() + .1f;
					imp.mulLocal(-100.0f*b.getMass() / (dist*dist)); // gives a pretty good blast
					
					/* Boom!  Send the nearby objects flying.  Later on we'll add more fun stuff here. */
					b.applyImpulse(imp, b.getWorldCenter());
					
				}
			}
			
		}

	}

}